package com.knife.database.backup.mysql;

import com.knife.common.constants.BaseConstants;
import com.knife.database.backup.KnifeDbBackUp;
import com.knife.database.backup.concurrent.KnifeDbBackThreadPool;
import com.knife.database.backup.config.KnifeDbBackUpProperties;
import com.knife.database.backup.constants.KnifeDialect;
import com.knife.database.backup.utils.KnifeSqlUtil;
import com.knife.file.app.KnifeFileInfoService;
import com.knife.file.compress.KnifeFileCompress;
import com.knife.file.domain.entity.KnifeFileInfo;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.ObjectUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * mysql备份
 *
 * @author： 76875
 * @date： 2022/3/31 星期四 9:39
 * @description：
 * @modifiedBy：
 * @version: 1.0
 */

public class MySqlKnifeDbBackUp implements KnifeDbBackUp {
    public static final Logger LOGGER = LoggerFactory.getLogger(MySqlKnifeDbBackUp.class);

    private JdbcTemplate jdbcTemplate;
    private KnifeDbBackUpProperties knifeDbBackUpProperties;
    private KnifeFileCompress knifeFileCompress;
    private KnifeFileInfoService knifeFileInfoService;

    public KnifeFileCompress getKnifeFileCompress() {
        return knifeFileCompress;
    }

    public KnifeFileInfoService getKnifeFileInfoService() {
        return knifeFileInfoService;
    }

    public void setKnifeFileInfoService(KnifeFileInfoService knifeFileInfoService) {
        this.knifeFileInfoService = knifeFileInfoService;
    }

    public void setKnifeFileCompress(KnifeFileCompress knifeFileCompress) {
        this.knifeFileCompress = knifeFileCompress;
    }

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public KnifeDbBackUpProperties getKnifeDbBackUpProperties() {
        return knifeDbBackUpProperties;
    }

    public void setKnifeDbBackUpProperties(KnifeDbBackUpProperties knifeDbBackUpProperties) {
        this.knifeDbBackUpProperties = knifeDbBackUpProperties;
    }

    @Override
    public void backUp() {
        // 获取表名
        String dbName = knifeDbBackUpProperties.getDbName();
        String tempDir = knifeDbBackUpProperties.getTempDir();
        List<String> onlyTableNames = knifeDbBackUpProperties.getOnlyTableNames();
        List<String> ignoreTableNames = knifeDbBackUpProperties.getIgnoreTableNames();
        List<String> tableNames = jdbcTemplate.queryForList(
                "select TABLE_NAME from information_schema.tables where table_schema = " + BaseConstants.SqlSymbol.SINGLE_QUOTATION + dbName + BaseConstants.SqlSymbol.SINGLE_QUOTATION,
                String.class
        );
        // 获取本次准备写入的目录
        File file = new File(tempDir);
        if (file.exists() || file.mkdir()) {
            Date date = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat(BaseConstants.Pattern.NONE_DATETIME);
            String postFix = sdf.format(date);
            // 处理获取需要处理的表
            List<String> backUpTableList = new ArrayList<>();
            for (String tableName : tableNames) {
                if (!ObjectUtils.isEmpty(onlyTableNames) && !onlyTableNames.contains(tableName)) {
                    // 不处理
                } else if (!ObjectUtils.isEmpty(ignoreTableNames) && ignoreTableNames.contains(tableName)) {
                    // 不处理
                } else {
                    backUpTableList.add(tableName);
                }
            }
            if (Objects.nonNull(knifeDbBackUpProperties.getAllIn()) && knifeDbBackUpProperties.getAllIn()) {
                backupBySingleThread(backUpTableList, tempDir);
            } else {
                // 多线程
                backupByMultiPartThread(backUpTableList, tempDir);
            }
            String zipFileName = dbName + postFix + ".zip";
            knifeFileCompress.compress(file, zipFileName, true);
            KnifeFileInfo zipFileUrl = knifeFileInfoService.uploadFile(BaseConstants.DEFAULT_TENANT_ID,
                    "knife",
                    null,
                    "db",
                    zipFileName,
                    0,
                    "minio",
                    zipFileName);
            LOGGER.info("文件备份信息:{}",zipFileUrl);
            // 删除文件
            try {
                FileUtils.deleteDirectory(file);
                Path path = Paths.get(zipFileName);
                FileUtils.delete(path.toFile());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void backupByMultiPartThread(List<String> backUpTableList, String tempDir) {
        ThreadPoolExecutor threadPoolExecutor = KnifeDbBackThreadPool.getThreadPoolExecutor();
        CountDownLatch countDownLatch = new CountDownLatch(backUpTableList.size());
        for (String tableName : backUpTableList) {
            threadPoolExecutor.execute(() -> {
                List<Map<String, Object>> queryForList = jdbcTemplate.queryForList(
                        "select * from " + tableName
                );
                // 只有一个元素，并且map第一个键为Table，第二个键为Create Table
                List<Map<String, Object>> ddl = jdbcTemplate.queryForList("show create table " + tableName);
                List<String> sqlList = new ArrayList<>();
                sqlList.add("drop table if exists " + ddl.get(0).get("Table").toString() + BaseConstants.Symbol.SEMICOLON);
                sqlList.add(ddl.get(0).get("Create Table").toString() + BaseConstants.Symbol.SEMICOLON);
                for (Map<String, Object> map : queryForList) {
                    String insertSql = KnifeSqlUtil.createInsertSql(KnifeDialect.mysql, tableName, map);
                    sqlList.add(insertSql);
                }
                File sqlFile = new File(tempDir + BaseConstants.Symbol.SLASH + tableName + ".sql");
                FileOutputStream fos = null;
                try {
                    fos = new FileOutputStream(sqlFile);
                    fos.write(String.join("\n", sqlList).getBytes(StandardCharsets.UTF_8));
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (Objects.nonNull(fos)) {
                            fos.flush();
                            fos.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    countDownLatch.countDown();
                }
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void backupBySingleThread(List<String> backUpTableList, String tempDir) {
        List<String> sqlList = new ArrayList<>();
        for (String tableName : backUpTableList) {
            List<Map<String, Object>> queryForList = jdbcTemplate.queryForList(
                    "select * from " + tableName
            );
            // 只有一个元素，并且map第一个键为Table，第二个键为Create Table
            List<Map<String, Object>> ddl = jdbcTemplate.queryForList("show create table " + tableName);
            sqlList.add("drop table if exists " + ddl.get(0).get("Table").toString() + BaseConstants.Symbol.SEMICOLON);
            sqlList.add(ddl.get(0).get("Create Table").toString() + BaseConstants.Symbol.SEMICOLON);
            for (Map<String, Object> map : queryForList) {
                String insertSql = KnifeSqlUtil.createInsertSql(KnifeDialect.mysql, tableName, map);
                sqlList.add(insertSql);
            }
        }
        File sqlFile = new File(tempDir + BaseConstants.Symbol.SLASH + tempDir + ".sql");
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(sqlFile);
            fos.write(String.join("\n", sqlList).getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (Objects.nonNull(fos)) {
                    fos.flush();
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
